<?php
/**
 * yui datatable functionality
 *
 * PHP version 5
 *
 * @category  PHP
 * @package   PHPMY
 * @author    Sven Ruttloff, www.sruttloff.de <service@sruttloff.de>
 * @copyright 2010 Sven Ruttloff, www.sruttloff.de
 * @license   http://www.sruttloff.de/cms/files/licence_en_lgpl.txt LGPL
 * @version   SVN: $Id: yui.datatable.class.php 114 2010-08-12 09:17:05Z sruttloff@googlemail.com $
 * @link      phpmy
 */

/**
 * yui datatable functionality
 *
 * @category  PHP
 * @package   ERP_Module_Skeleton
 * @author    Sven Ruttloff, www.sruttloff.de <service@sruttloff.de>
 * @copyright 2010 Sven Ruttloff, www.sruttloff.de
 * @license   http://www.sruttloff.de/cms/files/licence_en_lgpl.txt LGPL
 * @link      phpmy
 */
class yuiDatatable extends mvc
{
    protected $VIEWTABLESTART = '{"recordsReturned"::#:recordsReturned:#:,"totalRecords"::#:totalRecords:#:,
        "startIndex"::#:startIndex:#:,"sort":":#:sort:#:","dir":":#:dir:#:","pageSize"::#:pageSize:#:,"records":[';
    protected $VIEWTABLEEND = ']}';
    protected $a_TXTELS = array(
        'HeadShowItems' => 'Showing items',
        'of' => 'of',
        'prev' => '< prev',
        'next' => 'next >',
        'MSG_ERROR' => 'Data error',
        'MSG_EMPTY' => 'No records found',
        'MSG_SORTASC' => 'Click to sort ascending',
        'MSG_SORTDESC' => 'Click to sort descending'
    );
    /**
     * unique element id
     *
     * @var string
     */
    protected $s_DOMELEMENTID = null;
    /**
     * row on click handler function
     *
     * @var string
     */
    protected $s_ONROWSELECT = null;
    /**
     * width of datatable, optional
     *
     * @var string
     */
    protected $DTWIDTH = null;
    /**
     * heigth of datatable, optional
     *
     * @var string
     */
    protected $DTHEIGHT = null;
    /**
     * datasource, xhr request source
     *
     * @var string
     */
    protected $s_DATASOURCE = null;
    /**
     * create the root entry.
     *
     * @param string $name   unique datatable name
     * @param object $handle db handle
     * @param string $table  db table name
     *
     * @return void
     */
    public function __construct($name = 'unique', $handle = null, $table = null)
    {
        return $this->mvc($name, $handle, $table);
    }
    /**
     * columns to select from DB table
     *
     * @param string|array $cols array or string of col.name(s)
     *
     * @return      void
     */
    public function modelSetTableColumns($cols = null)
    {
        if (is_array($cols))
        {
            foreach ($cols as $column)
            {
                // as included
                if (preg_match('=\sAS\s=i', $column))
                {
                    $tmp = explode(' as ', $column);
                    if (count($tmp) != 2)
                        $tmp = explode(' AS ', $column);
                    if (count($tmp) == 2)
                    {
                        $this->DBTABLECOLUMNS[$tmp[1]] = $column;
                        continue;
                    }
                }
                $this->DBTABLECOLUMNS[$column] = $column;
            }
        }
        elseif ($cols) $this->DBTABLECOLUMNS[$cols] = $cols;
        $this->VIEWDATASETOUTPUT = null;
    }
    /**
     * return sort column
     *
     * @return string sort column
     */
    public function getSortBy()
    {
        if ($this->ViewGetSortBy())
            return $this->ViewGetSortBy();
        // set first row
        foreach ($this->DBTABLECOLUMNS as $col=>$v)
        {
            if (in_array($col, $this->VIEWDONTSHOWCOLUMNS)
                || in_array($col, $this->VIEWDONTSORTCOLUMNS)
            )
                continue;
            $this->ViewSetSortBy($col, 'ASC');
            break;
        }
        return $this->ViewGetSortBy();
    }
    /**
     * set a text fragment to user defined one
     *
     * @param string $s_txtElement element name
     * @param string $s_content    content of fragment
     *
     * @return bool
     */
    public function setTextContent ($s_txtElement = null, $s_content = null)
    {
        if (!$s_txtElement)
            return false;
        $this->a_TXTELS[$s_txtElement] = $s_content;
        return true;
    }
    /**
     * set a row click handler js function to call in click
     *
     * @param string $s_functionname js function name to call
     *
     *  @return void;
     */
    public function setOnRowSelect ($s_functionname)
    {
        $this->s_ONROWSELECT = $s_functionname;
    }
    /**
     * set datatable width
     *
     * @param string $s_width width of dt in px,em,%,...
     *
     * @return bool
     */
    public function setWidth ($s_width)
    {
        $this->DTWIDTH = $s_width;
        return true;
    }
    /**
     * set datatable heigth
     *
     * @param string $s_height height of dt in px,em,%,...
     *
     * @return bool
     */
    public function setHeight ($s_height)
    {
        $this->DTHEIGHT = $s_height;
        return true;
    }
    /**
     * set or get datasource
     *
     * @param string $s optional, if empty return datasource if set
     *
     * @return string
     */
    public function dataSource($s = null)
    {
        if ($s)
        {
            // append & if not set
            if (!preg_match('=&$=', $s)) $s .= '&';
            $this->s_DATASOURCE = $s;
        }
        return $this->s_DATASOURCE;
    }
    /**
     * return js
     *
     * @param string $domElementId     dome element to render in
     * @param bool   $b_includeExecute include execution of function, def. = true
     *
     * @return string the function name to init is yuiDT[DomElementId]
     */
    public function render($domElementId = '', $b_includeExecute = true)
    {
        //$this->setContext();
        $this->s_DOMELEMENTID = $domElementId;
        $s = null;
        // get colmn def
        $s .= $this->renderColDefinition();
        $s .= $this->renderDataSource();
        $s .= $this->renderResponseSchema();
        $s .= $this->renderConfiguration();
        $s .= $this->renderDataTableInstance();
        $s = 'var yuiDT' . $this->s_DOMELEMENTID . ' = function(skipInitLoad) {
            var fireXhr = function () {
            var oCallback = {
                success : myDataTable.onDataReturnInitializeTable,
                failure : myDataTable.onDataReturnAppendRows,
                scope : myDataTable,
                argument: myDataTable.getState() // data payload that will be returned to the callback function
            };
            var request = myDataTable.get("generateRequest")(myDataTable.getState());
            myDataSource.sendRequest(request, oCallback);
        }; '
        . $s . ' };';
        if ($b_includeExecute)
            $s .= 'YAHOO.util.Event.addListener(window, "load", yuiDT' . $this->s_DOMELEMENTID . ');';
        return $s;
    }
    /**
     * return js instance
     *
     * @return string
     */
    protected function renderDataTableInstance()
    {
        $s = '
        // DataTable instance
    var myDataTable = new YAHOO.widget.' . ($this->DTWIDTH || $this->DTHEIGHT ? 'ScrollingDataTable' : 'DataTable')
        . '("' . $this->s_DOMELEMENTID . '", myColumnDefs, myDataSource, myConfigs);
    // Update totalRecords on the fly with value from server
    myDataTable.handleDataReturnPayload = function(oRequest, oResponse, oPayload) {
        oPayload.totalRecords = oResponse.meta.totalRecords;
        return oPayload;
    }'
        . ($this->s_ONROWSELECT ?
        '
        myDataTable.subscribe("rowMouseoverEvent", myDataTable.onEventHighlightRow);
        myDataTable.subscribe("rowMouseoutEvent", myDataTable.onEventUnhighlightRow);
        var mySelectRow = function () {
            var data = this.getRecordSet().getRecord(this.getSelectedRows()[0])._oData;
            ' . $this->s_ONROWSELECT . ' (data, this);
            return true;
        }
        myDataTable.subscribe("rowClickEvent", myDataTable.onEventSelectRow);
        myDataTable.subscribe("rowSelectEvent", mySelectRow);
        ' : '')
        . '
    // fire initial xhr request
    if (!skipInitLoad) fireXhr();
    return { ds: myDataSource, dt: myDataTable, fire: fireXhr};
        ';
        return $s;
    }
    /**
     * return js configuration
     *
     * @return string
     */
    protected function renderConfiguration()
    {
        $s_sort = $this->getSortBy();
        $s_sortDirection = $this->getSortByDirection();
        $i_rowsPerPage = $this->getDisplayLimit();
        $s = '// DataTable configuration
        var myRequestBuilder = function(oState, oSelf) {
            // Get states or use defaults
            oState = oState || { pagination: null, sortedBy: null };
            var sort = (oState.sortedBy) ? oState.sortedBy.key : "myDefaultColumnKey";
            var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "DESC" : "ASC";
            var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
            var results = (oState.pagination) ? oState.pagination.rowsPerPage : 100;
            // Build custom request
            return  "sort=" + sort +
                    "&dir=" + dir +
                    "&startIndex=" + startIndex +
                    "&results=" + results +
                    "&dtID=' . $this->getMVCName() . '";
        };
    var myConfigs = {'
        . ($this->DTWIDTH ? 'width:"' . $this->DTWIDTH . '",' : '')
        . ($this->DTHEIGHT ? 'height:"' . $this->DTHEIGHT . '",' : '')
        . 'MSG_SORTASC: "' . $this->a_TXTELS['MSG_SORTASC'] . '",'
        . 'MSG_SORTDESC: "' . $this->a_TXTELS['MSG_SORTDESC'] . '",'
        . 'MSG_ERROR: "' . $this->a_TXTELS['MSG_ERROR'] . '",'
        . 'MSG_EMPTY: "' . $this->a_TXTELS['MSG_EMPTY'] . '",'
        . '
        //initialRequest: "sort=' . $s_sort . '&dir=' . $s_sortDirection . '&startIndex=0&results=' . $i_rowsPerPage
        . '", // Initial request for first page of data
        dynamicData: true, // Enables dynamic server-driven data
        generateRequest: myRequestBuilder,
        initialLoad: false,
        sortedBy : {key:"' . $s_sort . '", dir:YAHOO.widget.DataTable.CLASS_'
        . strtoupper($s_sortDirection) . '}, // Sets UI initial sort arrow
        paginator: new YAHOO.widget.Paginator({ rowsPerPage:' . $i_rowsPerPage
            . ',rowsPerPageOptions : [10,25,50,100,250,500,1000] '
            . ',previousPageLinkLabel : "' . $this->a_TXTELS['prev'] . '"'
            . ',nextPageLinkLabel  : "' . $this->a_TXTELS['next'] . '"'
            . ',template : "{PreviousPageLink} {CurrentPageReport} {NextPageLink} {RowsPerPageDropdown}" '
            . ',pageReportValueGenerator : function (paginator) { var recs  = paginator.getPageRecords();'
            . 'return { startIndex: recs[0]+1, endIndex: recs[1]+1, totalRecords: paginator.getTotalRecords()};}'
            . ',pageReportTemplate : "' . $this->a_TXTELS['HeadShowItems'] . ' {startIndex} - {endIndex} '
            . $this->a_TXTELS['of'] . ' {totalRecords}" '
            . '}) // Enables pagination
    };
        ';
        return $s;
    }
    /**
     * return js datasource
     *
     * @return string
     */
    protected function renderDataSource()
    {
        $s = '
        var myDataSource = new YAHOO.util.DataSource("' . $this->s_DATASOURCE . '");
        ';
        return $s;
    }
    /**
     * return js response schema
     *
     * @return string
     */
    protected function renderResponseSchema()
    {
        $a = array();
        $x = 0;
        foreach ($this->PREPENDCOLUMNS as $k => $v)
        {
            $x++;
            $a[] = '{key:"prepend' . $x . '"}';
        }
        foreach ($this->DBTABLECOLUMNS as $k=>$col)
        {
            $a[] = '{key:"' . $k . '"}';
        }
        $x = 0;
        foreach ($this->APPENDCOLUMNS as $k => $v)
        {
            $x++;
            $a[] = '{key:"append' . $x . '"}';
        }
        $s = 'myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
        var DScallback = function ( oRequest , oFullResponse , oCallback) {
            phpmyhandleSuccess({ responseText: YAHOO.lang.JSON.stringify(oFullResponse)});
            if (! oFullResponse.dt' . $this->getMVCName() . 'Data) return "null";
            return oFullResponse.dt' . $this->getMVCName() . 'Data;
        }
        myDataSource.doBeforeParseData = DScallback;
    myDataSource.responseSchema = {
        resultsList: "records",
        fields: [' . implode(',', $a) . '],
        metaFields: {
            totalRecords: "totalRecords",
            paginationRecordOffset : "startIndex",
            paginationRowsPerPage : "pageSize",
            sortKey: "sort",
            sortDir: "dir"
        }
    };
        ';
        return $s;
    }
    /**
     * return js colmn. def.
     *
     * @return string
     */
    protected function renderColDefinition()
    {
        $a = array();
        $x = 0;
        foreach ($this->PREPENDCOLUMNS as $k => $v)
        {
            $x++;
            $a[] = '{key:"prepend' . $x . '", label:"' . $k . '"}';
        }
        foreach ($this->DBTABLECOLUMNS as $k=>$col)
        {
            if (in_array($k, $this->VIEWDONTSHOWCOLUMNS))
                continue;
            $a[] = '{key:"' . $k . '", label:"'
                . (isset($this->BEAUTYDBTABLECOLUMNS[$k]) ? $this->BEAUTYDBTABLECOLUMNS[$k] : $col)
                . '"'
                . (isset($this->VIEWDONTSORTCOLUMNS[$k]) ? '' : ', sortable:true')
                . '}';
        }
        $x = 0;
        foreach ($this->APPENDCOLUMNS as $k => $v)
        {
            $x++;
            $a[] = '{key:"append' . $x . '", label:"' . $k . '"}';
        }
        return 'var myColumnDefs = [' . implode(',', $a) . '];';
    }
    /**
     * get json data and put direct to xhr sream
     *
     * @return bool
     */
    public function putJsonDataToXhr()
    {
        // put datas to xhr handler
        $xhr = xhranswer::getInstance();
        return $xhr->sendDataTableData($this->getMVCName(), $this->getJsonData());
    }
    /**
     * returns json data
     *
     * @return string
     */
    public function getJsonData()
    {
        $this->viewSetSortBy(sendvar('sort'), sendvar('dir'));
        $this->setDisplayLimit(sendvar('results'));
        $this->VIEWOFFSET = sendvar('startIndex');
        return $this->viewDataSet();
    }
    /**
     * create a html table from the entries
     *
     * @return  string  HTML Table
     */
    public function getHtmlData()
    {
        $return = parent::ViewDataSet();
        return $return;
    }
    /**
     * create a html table from the entries
     *
     * @return  string  HTML Table
     */
    public function viewDataSet()
    {
        $this->readDataFromSource();
        // header replacements
        $a = array (':#:MVCID:#:' => $this->MVCNAME,
            ':#:recordsReturned:#:' => count($this->DATASET),
            ':#:totalRecords:#:' => $this->viewGetDatasTotal(),
            ':#:startIndex:#:' => $this->VIEWOFFSET,
            ':#:sort:#:' => $this->getSortBy(),
            ':#:dir:#:' => $this->getSortByDirection(),
            ':#:pageSize:#:' => $this->getDisplayLimit()
        );
        $output = str_replace(array_keys($a), $a, $this->VIEWTABLESTART);
        $s_datas = null;
        if (isset($this->DATASET))
        {
            reset($this->DATASET);
            foreach ($this->DATASET as $object)
            {
                $s_datas .= ($s_datas ? ', ' : '') . '{';
                $a_datas = array();
                // man. prep. cols
                $x = 0;
                foreach ($this->PREPENDCOLUMNS as $k => $v)
                {
                    $x++;
                    $a_datas[] = '"prepend' . $x . '":"'
                        . $this->quoteResponseValue(call_user_func($v, $this, $object)) . '"';
                }
                foreach ($object as $k => $v)
                {
                    $m_cHandler = isset(
                        $this->VIEWCOLUMNHANDLERFUNCTIONS[$k]) ? $this->VIEWCOLUMNHANDLERFUNCTIONS[$k] : null;
                    $a_datas[] = '"' . $k . '":"'
                        . $this->quoteResponseValue(
                            ($m_cHandler ?
                            $this->executeColumnHandler($m_cHandler, $v, $object) :
                            HtmlFormat($v))
                        ) . '"';
                }
                // man. app. cols
                $x = 0;
                foreach ($this->APPENDCOLUMNS as $k => $v)
                {
                    $x++;
                    $a_datas[] = '"append' . $x . '":"'
                        . $this->quoteResponseValue(call_user_func($v, $this, $object)) . '"';
                }
                $s_datas .= implode(',', $a_datas) . '}';
            }
        }
        $output .= $s_datas . $this->VIEWTABLEEND;
        $this->VIEWDATASETOUTPUT = $output;
        return $this->VIEWDATASETOUTPUT;
    }
    /**
     * escape unallowed chars for response
     *
     * @param string $s string to escape
     *
     * @return string
     */
    protected function quoteResponseValue ($s)
    {
        $a = array ('\\' => '\\\\', '"' => '\"', "\r\n" => '', "\n" => '');
        return str_replace(array_keys($a), $a, nl2br($s));
    }
    /**
     * execute external function and return value
     *
     * @param string|object $m_function function or object methode to execute
     * @param string        $value      value of column
     * @param object        $object     data object
     *
     * @return string
     */
    protected function executeColumnHandler($m_function, $value, $object)
    {
        return call_user_func($m_function, $this, $value, $object);
    }
}